#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <regex.h>
#include "./ftp_def.h"

int sock_con = 0, sock_data = 0;
int transfer_mode = 0;
struct sockaddr_in serv_addr_con, serv_addr_data;

// funciton declaration
int create_socket(const int net_protocol, const int trans_protocol, const int ip, 
                 const int port, struct sockaddr_in* serv_addr);
int create_socket_fd(int sock, struct sockaddr_in serv_addr);
int check_command_result(const char* user_command);
int dirname_validation(char* user_dirname);
int command_help();
int command_quit();
int command_ls();
int command_get(char* user_command);
int command_put(char* user_command);
int command_delete(char* user_command);
int command_cd(char* user_command);
int command_mkdir(char* user_command);
int command_pwd();
int command_set(char* user_command); 
int create_sock_and_sockfd();
void close_socks();

// Create socket connection:
// AF_INET: IPV4
// SOCK_STREAM: TCP
// 0:Protocol value for Internet Protocol(IP)
int create_socket(const int net_protocol, const int trans_protocol, const int ip, 
                 const int port, struct sockaddr_in* serv_addr){
    int sock = 0;
    
	if ((sock = socket(net_protocol, trans_protocol, ip)) < 0) {
		printf("\n Socket creation error \n");
		return -1;
	}

    serv_addr->sin_family = net_protocol;
	serv_addr->sin_port = htons(port);

    // Convert IPv4 and IPv6 addresses from text to binary form
    if (inet_pton(net_protocol, "127.0.0.1", &(serv_addr->sin_addr))<= 0) {
		printf("\nInvalid address/ Address not supported \n");
		return -1;
	}
    return sock;
}

int create_socket_fd(int sock, struct sockaddr_in serv_addr){
    int client_fd = connect(sock, (struct sockaddr*)&serv_addr,sizeof(serv_addr));
    if ( client_fd < 0) {
		printf("\nConnection Failed in create sockfd. The sock is %d\n",sock);
		return -1;
	}else{
        printf("Create sockfd...\n");
    }
    return client_fd;
}

int check_command_result(const char* user_command){
    int bytes;
    char buffer[BYTE_LENGTH] = { 0 };

    // read relply code from control port
    if( (bytes = recv(sock_con, buffer, BYTE_LENGTH, 0)) < 0){
        printf("read reply code from con port: error.\n");
        return -1;
    }

    // check reply code
    // if reply code is not 200 : return 1
    if (strncmp(buffer,"200",3) != 0){
        printf("%s\n",buffer);
        return 1;
    }
    return 0;
}

int dirname_validation(char* user_dirname){
    
    regex_t reg;
    // define pattern
	const char * pattern = "^[^\\/:*?""<>|]+$";
    // complie regex
	regcomp(&reg,pattern,REG_EXTENDED);
    // compare pattern to dirname
	int status = regexec(&reg, user_dirname, 0, NULL, 0);
    regfree(&reg);

    // if dirname has no pattern
    if (status == REG_NOMATCH){
        return -1;
    }
    else return 0;
    
}

int command_help(){
    printf("****************** user help ******************\n");
    printf("*\n");
    printf("* 'help': get user help\n");
    printf("*\n");
    printf("* 'get [remote filename]': copy file from remote to local directory\n");
    printf("*\n");
    printf("* 'put [local  filename]': copy file from local to remote directory\n");
    printf("*\n");
    printf("* 'delete [remote filename]': delete file from remote directory \n");
    printf("*\n");
    printf("* 'ls': list the files and subdirectories in the remote directory\n");
    printf("*\n");
    printf("* 'cd [path]': change working directory to [path]\n");
    printf("*\n");
    printf("* 'mkdir [remote directory name]': create directory on remote machine\n");
    printf("*\n");
    printf("* 'pwd': print current working directory\n");
    printf("*\n");
    printf("* 'quit': terminate ftp session.\n");
    printf("*\n");
    printf("* 'set binary' or 'set ascii': set your transfer mode of file to binary or ascii.\n");
    printf("*\n");
    printf("* Your transfer mode of files is %s now.\n",get_transfer_mode(transfer_mode));
    printf("****************** end help ******************\n");
    return 0;
}

int command_quit(){
    if (send(sock_con, QUIT, strlen(QUIT), 0) < 0){
        printf("send < quit > to control port : error.\n");
        return -1;
    }
    close_socks();
    printf("Bye!\n");
    return 0;
}

int command_ls(){
    int bytes;
	char buffer[BYTE_LENGTH] = { 0 };
    
    // send message from control port
    if (send(sock_con, LS, strlen(LS), 0) < 0){
        printf("send < ls > to control port : error.\n");
        return -1;
    }

    // read message from data port
	if ((bytes = recv(sock_data, buffer, BYTE_LENGTH, 0)) < 0){
        // if fail, print the wrong tips
        printf("read ls message from data port: error.\n");
        return -1;
    }
    if (bytes >= 0){
        // if run successfully, print buffer
        printf("%s\n", buffer);
    }

    // print reply code
    return check_command_result("ls");
}

int command_get(char* user_command){
    int bytes;
	char buffer[BYTE_LENGTH] = { 0 };
    
    // get local file opened
    char path[BYTE_LENGTH] = { 0 };
    char filename[BYTE_LENGTH] = { 0 };
    strncpy(path, user_command + 4, BYTE_LENGTH);
    // delete blank 
    remove_space_both_ends(path);
    // delete path of dirs
    parse_filename_from_path(path, filename);
    // get the unique filename
    get_aliant_filename(filename);

    FILE * this_file = NULL;
    // create local file
    if ((this_file = fopen(filename, get_write_mode(transfer_mode))) == NULL){
        printf("cann't create file %s.\n",filename);
        return -1;
    }

    // send message from control port
    if ( send(sock_con, user_command, strlen(user_command), 0) < 0 ){
        printf("send < %s > to control port : error.\n", user_command);
        remove(filename);
        return -1;
    }

    // read recive times from con port
    // recv_times = 0 represent server not found the file
    int recv_size = 0, recv_times = 0;
    if(recv(sock_con, &recv_times, sizeof(int), 0) < 0){
        printf("read command's reply from con port: error.\n");
        fclose(this_file);
        remove(filename);
        return -1;
    }  
    // fail to get remote file
    if(recv_times == 0){
        printf("can't get %s in server!\n", user_command+4);
        fclose(this_file);
        remove(filename);
        return 0;
    }

    printf("* your file is named \033[1;32m%s\033[0m\n", filename);

    // set print color to green
    printf("\033[1;32m");

    int len = 0;
    // receive file from data port
    for(int i=0;i<recv_times;i++){
        if ( (recv_size = recv(sock_data, buffer, BYTE_LENGTH, 0))<0){
            printf("recieving data error! Delete file %s form local.\n",filename);
            fclose(this_file);
            remove(filename);
            return -1;
        }
        len = printf("Transfering data %d/%d",i+1,recv_times);
		if(i < recv_times-1){
			printf("\033[%dD\033[K",len);
		}
        fwrite(buffer,sizeof(char),recv_size,this_file);
        bzero(&buffer, sizeof(buffer));
    }

    // set print color to none
    printf("\033[0m\n");
    // Close the file pointer
    fclose(this_file);

    // print reply code
    return check_command_result(user_command);
}

int command_put(char* user_command){
    int bytes;
    char filename[N] = { 0 };
	char buffer[BYTE_LENGTH] = { 0 };

    // check file exists
    strcpy(filename, user_command + 4);
    remove_space_both_ends(filename);

    FILE* fd=NULL;

    // read file (read-only)
    if((fd=fopen(filename, get_read_mode(transfer_mode))) == NULL){
        printf("File %s not exists or have no authority!\n", user_command+4);
        return 0;
    }

    // send message from control port
    if ( send(sock_con, user_command, strlen(user_command), 0) < 0 ){
        printf("send <%s> to control port : error.\n", user_command);
        return -1;
    }
    // check file extablished
	if( recv(sock_con, buffer, BYTE_LENGTH, 0) < 0 ){
        printf("recv filename error\n");
        return -1;
    }
    // get 502: establish file failed
    if (strncmp(buffer,"502",3) == 0) {
        printf("%s\n",buffer); 
        return 0;
    }
    // get filename: establish file successfully
    printf("* filename at server is \033[1;32m%s\033[0m\n",buffer);

    // send file content to server

    // Firstly, count and send the transfer times of this file
    int send_times = 0;
    while(fread(buffer,sizeof(char),BYTE_LENGTH,fd) > 0){
        send_times++ ;
    }
    if(send(sock_con, &send_times, sizeof(int), 0) < 0){
        printf("send receive times error\n");
        return -1;
    }

    // Secondly, send file content
    int sent_data = 0;
    rewind(fd);
    int print_len = 0;
    while((bytes=fread(buffer,sizeof(char),BYTE_LENGTH,fd)) > 0){
        sent_data ++;
        if(send(sock_data, buffer, bytes, 0) < 0){
            print_len = printf("\033[1;31mwrite: error!\033[0m\n");
            fclose(fd);
            return -1;
        }else{
            print_len = printf("\033[1;32msending data %d/%d\033[0m",sent_data,send_times);
            (sent_data < send_times)?printf("\033[%dD\033[K",print_len):printf("\n");  
        }
    }
    fclose(fd);

    // print reply code 
    return check_command_result(user_command);
}

int command_delete(char* user_command){
    int bytes;
	char buffer[BYTE_LENGTH] = { 0 };

    // send message from control port
    if ( send(sock_con, user_command, strlen(user_command), 0) < 0 ){
        printf("send < %s > to control port : error.\n", user_command);
        return -1;
    }

    // print reply code
    return check_command_result(user_command);
}

int command_cd(char* user_command){
    int bytes;
	char buffer[BYTE_LENGTH] = { 0 };

    // send message from control port
    if ( send(sock_con, user_command, strlen(user_command), 0) < 0 ){
        printf("send < %s > to control port : error.\n", user_command);
        return -1;
    }

    // read message from data port
	if ((bytes = recv(sock_data, buffer, BYTE_LENGTH, 0)) < 0){
        //if fail, print the wrong tips
        printf("read cd message from data port: error.\n");
        return -1;
    }

    // print reply code
    return check_command_result(user_command);
}

int command_mkdir(char* user_command){
    int bytes;
	char buffer[BYTE_LENGTH] = { 0 };

    // check the directory name validation
    if (dirname_validation(user_command+6) != 0){
        printf("invalid directory name!\n");
        return 0;
    }
    // send message from control port
    if (send(sock_con, user_command, strlen(user_command), 0) < 0){
        printf("send < %s > to control port : error.\n", user_command);
        return -1;
    }

    // print reply code
    return check_command_result(user_command);
}

int command_pwd(){
    int bytes;
	char buffer[BYTE_LENGTH] = { 0 };

    // send message from control port
    if ( send(sock_con, PWD, strlen(PWD), 0) < 0 ){
        printf("send < pwd > to control port : error.\n");
        return -1;
    }

    // read message from data port
	if ((bytes = recv(sock_data, buffer, BYTE_LENGTH, 0)) < 0){
        // if fail, print the wrong tips
        printf("read pwd message from data port: error.\n");
        return -1;
    }
    if (bytes >= 0){
        // if run successfully, print buffer
        printf("%s\n", buffer);
    }

    // print reply code
    return check_command_result("pwd");
}

int command_set(char* user_command){
    int mode_n = 0;
    char mode[N] = {0};
    char buffer[BYTE_LENGTH] = {0};

    // transfer_mode is modified
    mode_n = set_transfer_mode(&transfer_mode, user_command); 

    // transfer_mode will be modified if mode_n == 1
    // mode_n == -1 illegal
    // mode_n == 0 remain the same
    // promot have been printed in set_transfer_mode
    if( mode_n < 1){
        return 0;
    }
    if ( send(sock_con, user_command, BYTE_LENGTH, 0) < 0 ){ 
        transfer_mode = 1 - transfer_mode;
        printf("send < %s > to control port : error.\n", user_command);
        return -1;
    }

    // recieve reply message from server
    // when reply code is not 200, command fail
    if ( check_command_result(user_command) == 1 ){
        transfer_mode = 1 - transfer_mode;
        printf("Your transfer mode remains to be \033[1;32m%s\033[0m.\n", get_transfer_mode(transfer_mode));
    }else{
        printf("Your transfer mode is set to \033[1;32m%s\033[0m.\n", get_transfer_mode(transfer_mode));
    }
    return 0;
}

int create_sock_and_sockfd(){
    if((sock_con = 
        create_socket(AF_INET, SOCK_STREAM, 0, PORT_CONTROL_MESSAGE, &serv_addr_con)) < 0){
        printf("control socket creation failed\n");
        return -1;
    }
    if((sock_data = 
        create_socket(AF_INET, SOCK_STREAM, 0, PORT_DATA, &serv_addr_data)) < 0){
        printf("control socket creation failed\n");
        return -1;
    }
    if (create_socket_fd(sock_con, serv_addr_con) < 0){
        return -1;
    } 
    if (create_socket_fd(sock_data, serv_addr_data) < 0){ 
        return -1;
    } 
    return 0;
}

void close_socks(){
    close(sock_con);
    close(sock_data);
}

int main(int argc, char const* argv[]){

    // create sock and sockfd
    int connect_result = create_sock_and_sockfd();
    if(connect_result < 0 ){
        printf("Cannot connect to server! Client shutdown!\n");
        return 0;
    }
    
    // Interact with user
    char user_command[N];
    int running = 1, result = 0;
    while(running){
        printf("\033[1;34mftp>\033[0m");
        if (fgets(user_command,N,stdin) == NULL){
            printf("Fgets: read error\n");
            return -1;
        }

        // repalce the '\n' to '\0'
        user_command[strlen(user_command)-1] = '\0'; 
        result = 0;
        
        // check command
        switch (check_command(user_command)){
            case C_HELP: result = command_help(); break;
            case C_QUIT: result = command_quit(); running = 0; break;
            case C_LS: result = command_ls(); break;
            case C_GET: result = command_get(user_command); break;
            case C_PUT: result = command_put(user_command); break;
            case C_DELETE: result = command_delete(user_command); break;
            case C_CD: result = command_cd(user_command); break;
            case C_MKDIR: result = command_mkdir(user_command); break;
            case C_PWD: result = command_pwd(); break;
            case C_SET: result = command_set(user_command); break;
            default: printf("Command < %s > not found.\n",user_command); 
        }

        // running == 0 but result == 1 means not successfully quit.
        // should retry too
        if( result == -1){
            printf("Send/Recv error for some reason. Your command < %s > unsuccess!\n",user_command);
            close_socks();
            printf("Reconnecting to server...\n");
            int connect_result = create_sock_and_sockfd();
            if(connect_result < 0 ){
                printf("Cannot connect to server! Client shutdown!\n");
                running = 0;
            } else{
                printf("You can retry you command!\n");
            }
        }
    }
	return 0;
}